# Copyright (c) 2008
#   Matt Pizzimenti (mjpizz@gmail.com, www.livelearncode.com)
#
# All rights reserved.
# 
# Redistribution and use in source and binary forms, with or without modification,
# are permitted provided that the following conditions are met:
# 
# Redistributions of source code must retain the above copyright notice,
# this list of conditions and the following disclaimer.
# 
# Redistributions in binary form must reproduce the above copyright notice,
# this list of conditions and the following disclaimer in the documentation
# and/or other materials provided with the distribution.
# 
# Neither the name of the original author nor the names of contributors
# may be used to endorse or promote products derived from this software
# without specific prior written permission.
# 
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
# ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
# LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
# DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
# SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
# CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
# OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

from users import User
from events import WarningEvent
from receipts import ReceiptManager
import exceptions


class ShellCommandFailed(exceptions.Exception): pass


class RoleResponse(object):
    """Encapsulates the responses from the Users in a particular Role.
    There is an stdout and stderr stream for each User.  As a
    convenience, the properties stdout and stderr are exposed for
    single-User Roles (these will throw ValueErrors for multi-user Roles)"""
    
    # FIXME: add the retcode
    
    def _get_stdout(self):
        """gets the stdout for single-User Roles
        (else, raises ValueError)"""
        keys = self._strategy_responses.keys()
        if len(keys) == 1:
            return self.stdout_for(keys[0])
        else:
            raise ValueError("more than one user participated in this response, use stdout_for(fullname) instead")

    def _get_stderr(self):
        """gets the stderr for a single-user Roles
        (else, raises ValueError)"""
        keys = self._strategy_responses.keys()
        if len(keys) == 1:
            return self.stderr_for(keys[0])
        else:
            raise ValueError("more than one user participated in this response, use stderr_for(fullname) instead")

    def _get_status(self):
        """gets the status for a single-user Roles
        (else, raises ValueError)"""
        keys = self._strategy_responses.keys()
        if len(keys) == 1:
            return self.status_for(keys[0])
        else:
            raise ValueError("more than one user participated in this response, use status_for(fullname) instead")
    
    cmd = property(lambda self: self._cmd)
    stdout = property(_get_stdout)
    stderr = property(_get_stderr)
    status = property(_get_status)
    
    def stdout_for(self, fullname):
        """gets the stdout (as a string) for a particular User that
        participated in this response"""
        if isinstance(fullname, User):
            user = fullname
        else:
            user = User(fullname)
        if user.fullname in self._strategy_responses:
            return self._strategy_responses[user.fullname].stdout
        else:
            raise KeyError("the user '%s' did not participate in this response" % user.fullname)

    def stderr_for(self, fullname):
        """gets the stderr (as a string) for a particular User that
        participated in this response"""
        if isinstance(fullname, User):
            user = fullname
        else:
            user = User(fullname)
        if user.fullname in self._strategy_responses:
            return self._strategy_responses[user.fullname].stderr
        else:
            raise KeyError("the user '%s' did not participate in this response" % user.fullname)

    def status_for(self, fullname):
        """gets the status (as a string) for a particular User that
        participated in this response"""
        if isinstance(fullname, User):
            user = fullname
        else:
            user = User(fullname)
        if user.fullname in self._strategy_responses:
            return self._strategy_responses[user.fullname].status
        else:
            raise KeyError("the user '%s' did not participate in this response" % user.fullname)
    
    def __init__(self, cmd):
        # TODO: private variables
        self._cmd = cmd
        self._strategy_responses = {}
    
    def add_strategy_response(self, user, strategy_response):
        """adds a response from a particular User's strategy
        (intended for internal use only) """
        self._strategy_responses[user.fullname] = strategy_response


class Role(object):
    
    def __init__(self, *fullnames):
        # TODO: private variables
        self.users = []
        for fullname in fullnames:
            if isinstance(fullname, User):
                self.users.append(fullname)
            else:
                self.users.append( User(fullname) )
    
    def run(self, cmd, ignore_failure = False):
        """runs a shell command for this Role"""
        role_response = RoleResponse(cmd)
        for user in self.users:
            strategy_response = user.strategy.exec_cmd(cmd)
            if strategy_response.status != 0:
                failure_message = "'%s' failed with status %s on '%s'" % (cmd, strategy_response.status, user.fullname)
                if ignore_failure:
                    # we are ignoring failures
                    ReceiptManager.get_instance().log_event( WarningEvent(failure_message) )
                else:
                    raise ShellCommandFailed(failure_message)
            role_response.add_strategy_response(user, strategy_response)
        return role_response
    
    def _delegate_call_to_all_user_strategies(self, method_name, *args, **kwargs):
        for user in self.users:
            getattr(user.strategy, method_name)(*args, **kwargs)
    
    def pull(self, local_filename, remote_filename):
        """copies a file from the local machine to the
        (likely remote) destination """
        return self._delegate_call_to_all_user_strategies("pull", local_filename, remote_filename)

    def expect_password_for(self, pattern):
        """adds a password expectation that the user must respond to"""
        return self._delegate_call_to_all_user_strategies("expect_password_for", pattern)

    def expect_input_for(self, pattern):
        """adds an input expectation that the user must respond to"""
        return self._delegate_call_to_all_user_strategies("expect_input_for", pattern)

    def respond_with(self, pattern, response):
        """adds an expectation that is automatically responded to"""
        return self._delegate_call_to_all_user_strategies("respond_with", pattern, response)

    def respond_with_password(self, pattern, response):
        """adds a password expecation that is automatically responded to"""
        return self._delegate_call_to_all_user_strategies("respond_with_password", pattern, response)
    
    def stop_expecting(self, pattern):
        """tells the strategy to stop expecting input for a
        particular pattern (password or input)"""
        return self._delegate_call_to_all_user_strategies("stop_expecting", pattern)
    
    def stop_responding_to(self, pattern):
        """tells the strategy to stop automatically responding for
        a particular pattern (password or input)"""
        return self._delegate_call_to_all_user_strategies("stop_responding_to", pattern)
            
